package controllers

import java.io.File
import java.nio.file.Files
import akka.stream.IOResult
import akka.stream.scaladsl.FileIO
import config.{Global, MyAwait, MyFile}
import dao.GrnaDao

import javax.inject.Inject
import models.Tables.GrnaRow
import org.apache.commons.io.FileUtils
import play.api.data.Form
import play.api.data.Forms._
import play.api.libs.json.Json
import play.api.libs.streams.Accumulator
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
import play.core.parsers.Multipart.{FileInfo, FilePartHandler}
import tools._
import utils.SendEmail.{Info, Sender}
import utils.{ExecCommand, SendEmail, TableUtils}

import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}
import scala.io.Source
import scala.util.Failure
import scala.jdk.CollectionConverters._

class ToolsController @Inject()(grnaDao: GrnaDao,
                                cc: ControllerComponents)
                               (implicit exec: ExecutionContext) extends AbstractController(cc) with MyFile with MyAwait {

  def toolsPage(page: String) = Action {
    Ok(
      page match {
        case "primerPage" => views.html.tools.primerPage()
        case "musclePage" => views.html.tools.musclePage()
        case "lastzPage" => views.html.tools.lastzPage()
        case "genewisePage" => views.html.tools.genewisePage()
        case "enrichPage" => views.html.tools.enrichPage()
        case "grnaPage" => views.html.tools.grnaPage()
        case "networkPage" => views.html.tools.networkPage()
        case "snpHeatmapPage" => views.html.tools.snpHeatmapPage()
        case "geneExpressionPage" => views.html.tools.geneExpressionPage()
        case "singleLocusPage" => views.html.tools.singleLocusPage()
        case "igcOrItcPage" => views.html.tools.igcOrItcPage()
      }
    )
  }

  private def handleFilePartAsFile: FilePartHandler[File] = {
    case FileInfo(partName, filename, contentType, dispositionType) =>
      val file = new File(Global.tmpPath, Global.random)
      val path = file.toPath
      Accumulator(FileIO.toPath(path)).mapFuture {
        case IOResult(_, Failure(error)) => Future.failed(error)
        case IOResult(count, _) =>
          Future.successful(FilePart(partName, filename, contentType, file, count, dispositionType))
      }
  }

  def toolsRun(tools: String): Action[AnyContent] = Action { implicit request =>
    Ok(
      tools match {
        case "genewise" => GeneWise.genewise
        case "enrich" => GoKegg.goOrKegg(grnaDao)
      }
    )
  }

  def toolsFileRun(tools: String) = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>
    Ok(
      tools match {
        case "muscle" => Muscle.muscle
        case "lastz" => Lastz.lastz
      }
    )
  }


  def primer = Action { implicit request =>
    var valid = "true"
    var message = ""
    var result = ""
    try {
      val data = Primer3.primerForm.bindFromRequest.get
      val data2 = Primer3.primerForm2.bindFromRequest.get
      val tmp = Files.createTempDirectory("tmpDir").toString

      val fa = data2.method match {
        case "range" =>
          val cmd = s"${Global.samtools} faidx ${Global.path}/fasta/genome/${data.species}.genome ${data.chr}:${data.start}-${data.end}"
          val execCommand = new ExecCommand
          execCommand.exect(Array(cmd), tmp)
          execCommand.getOutStr.split("\n").tail.mkString
        case "seq" =>
          data2.queryText
      }
      println(tmp)
      val res = Primer3.runPrimer(fa, data, tmp)
      valid = res._1
      message = res._2
      result = res._3
    } catch {
      case e: Exception =>
        valid = "false"
        message = e.getMessage
    }

    Ok(Json.obj("valid" -> valid, "message" -> message, "result" -> result))
  }

  def getGeneNameExample(genome: String) = Action { implicit request =>
    Ok(Json.toJson(TableUtils.exampleGeneName(genome).mkString(",")))
  }

  case class GrnaData(species: String, number: String,mail:String)

  val GrnaForm = Form(
    mapping(
      "species" -> text,
      "number" -> text,
      "mail" -> text,
    )(GrnaData.apply)(GrnaData.unapply)
  )

  def GrnaRun = Action(parse.multipartFormData(handleFilePartAsFile)) { implicit request =>

    val data = GrnaForm.bindFromRequest.get
    val f = disTmp(Global.random)

    val tmpDir = s"${Global.path}/tmp/$f"
    tmpDir.mkdir
    val row = GrnaRow(f, System.currentTimeMillis().toString, 0)
    grnaDao.insert(row).toAwait
    val genome = s"${Global.path}/fasta/cds/${data.species}.cds"
    val gene = s"$tmpDir/gene.fa"
    val file = request.body.file("file").get
    file.ref.moveToFile(gene.toFile)
    val command = s"perl ${Global.path}/tools/sgRNAfinder2022.pl -gene $gene -genome $genome -number ${data.number}"

    println(command)
    Future {
      val execCommand = new ExecCommand
      println("test")
      execCommand.exect(Array(command), tmpDir)
      println(execCommand.isSuccess)

    if (execCommand.isSuccess) {
        var fastaName = ""
        val gff = s"$tmpDir/sg_RNA.targs".readLines.map { x =>
          val line = x.split("\t")
          val chr = line.head
          fastaName = chr
          val start = line(2)
          val end = line(3)
          val strand = line(4)
          val id = chr + "_" + start + "_" + end
          val gc = line(5)
          val off = line.drop(6).headOption.getOrElse("").split(" ").map(_.trim).mkString(",")
          s"$chr\tGRNA\tgene\t$start\t$end\t.\t$strand\t.\tID=$id;GC%=$gc;Offtargs=$off;"
        }
        FileUtils.writeLines(s"$tmpDir/sg_RNA.gff".toFile, gff.asJava)
        val jbFa = s"/home/lzu_admin/project/alfalfa_db/tools/jb/bin/prepare-refseqs.pl --fasta $tmpDir/gene.fa --key $fastaName"
        val jbGff = s"/home/lzu_admin/project/alfalfa_db/tools/jb/bin/flatfile-to-json.pl --gff $tmpDir/sg_RNA.gff  --trackType FeatureTrack --trackLabel GRNA"
        val exec2 = new ExecCommand
        exec2.exect(Array(jbFa, jbGff), tmpDir)
        grnaDao.updateState(f, 1).toAwait
      } else {
        println(execCommand.getErrStr)
        grnaDao.updateState(f, 2).toAwait
      }

      val sender = Sender("", "smtp.126.com", "modmslzu2020@126.com", "DLUIIDMTBXEDIDHG")
      val info = Info("GRNA task："+ f +" is running completed", "Click https://modms.lzu.edu.cn/alfalfa/tools/grna/" + f +" to view the result page")
      SendEmail.sendEmailBySsl(sender, info, data.mail)

    }
    Ok(Json.toJson(f))
  }

  def disTmp(tmpDir: String): String = {
    if (s"${Global.path}/tmp/$tmpDir".toFile.exists) {
      disTmp(Global.random)
    } else {
      tmpDir
    }
  }

  def granInfoPage(file: String) = Action.async { implicit request =>
    grnaDao.getByFile(file).map { x =>
      Ok(views.html.tools.grnaInfoPage(x))
    }
  }

  def getGrnaData(file: String) = Action { implicit request =>
    val table = s"${Global.path}/tmp/$file/sg_RNA.targs".readLines.map(_.split("\t").map(_.trim)).map { x =>
      x.zipWithIndex.map { y =>
        y._2.toString -> y._1
      }.toMap
    }
    Ok(Json.toJson(table))
  }

  def getGrnaInfoByFile(file: String) = Action.async { implicit request =>
    grnaDao.getByFile(file).map { x =>
      Ok(Json.toJson(x.state))
    }
  }

  case class NetworkData(ids: String)

  val NetworkForm = Form(
    mapping(
      "ids" -> text
    )(NetworkData.apply)(NetworkData.unapply)
  )


  def networkRun = Action { implicit request =>
    val ids = NetworkForm.bindFromRequest.get.ids.split(",").map(_.trim)
    val path = if (Global.isWindow) {
      "H:\\紫花苜蓿数据库\\2022_12_19\\Cytoscape\\network2"
    } else {
      s"${Global.path}/config/network"
    }

    val cor = ids.filter(geneid => s"$path/$geneid.txt".toFile.exists()).flatMap { geneid =>
      s"$path/$geneid.txt".toFile.readLines.map(_.split("\t"))
    }.distinct

    val edges = cor.map { z =>
      val value = "Value:" + z.last
      Json.obj("from" -> z.head, "title" -> value, "to" -> z(1))
    }

    val n = ids.map { geneid =>
      Json.obj("id" -> geneid, "label" -> geneid, "group" -> "geneGroup", "title" -> geneid)
    }

    val nodes = (cor.map(_.head) ++ cor.map(_ (1))).distinct.filter(x => !ids.contains(x)).map { z =>
      Json.obj("id" -> z, "title" -> z)
    }.distinct

    Ok(Json.obj("edges" -> edges, "nodes" -> (n ++ nodes), "net" -> cor.map(z => z.zipWithIndex.map(y => y._2.toString -> y._1).toMap)))

  }

  case class RangeData(chr: String, start: Int, end: Int)

  val RangeForm = Form(
    mapping(
      "chr" -> text,
      "start" -> number,
      "end" -> number
    )(RangeData.apply)(RangeData.unapply)
  )

  def snpHeatmapRun = Action { implicit request =>
    val path = if (Global.isWindow) {
      "H:\\紫花苜蓿数据库\\2023_1_9/变异"
    } else {
      s"${Global.path}/config/snp_freq"
    }

    val data = RangeForm.bindFromRequest.get

    val chr = s"$path/snp_freq/${data.chr}.txt"
    val chrIndex = s"$path/snp_freq_index/${data.chr}.txt".readLines
    val lines = chrIndex.map(_.toInt).zipWithIndex.filter(x => x._1 >= data.start && x._1 <= data.end).map(_._2).take(2000)

    if (lines.nonEmpty) {

      val result = Source.fromFile(chr.toFile).getLines().slice(lines.head, lines.last).toSeq.map(_.split("\t"))
      val header = result.flatMap { x =>
        x.drop(4).map(_.split(":").head)
      }.distinct

      val freqMap = result.map { x =>
        val pos = x(1)
        val freq = x.drop(4).map(_.split(":")).map(y => y.head -> y.last).toMap
        Map("name" -> pos) ++ freq
      }

      val categories =   result.map(_(1))

      val series = header.map { x =>
        Json.obj("name" -> x, "data" -> freqMap.map(y => y.getOrElse(x, "0").toDouble))
      }

      Ok(Json.obj( "categories" -> categories,"series" -> series))

    } else {
      Ok(Json.obj("valid" -> "false", "msg" -> "No SNP information in the interval!"))
    }

  }


  def itcInfoPage(file: String) = Action.async { implicit request =>
    grnaDao.getByFile(file).map { x =>
      Ok(views.html.tools.itcInfoPage(x))
    }
  }

  def itcRun  = Action(parse.multipartFormData) { implicit request =>
    val params = request.body.asFormUrlEncoded.map { case (key, value) => key -> value.mkString(";") }
    val f = disTmp(Global.random)
    val path = s"${Global.path}/tmp/$f"
    path.mkdir

    val row = GrnaRow(f, System.currentTimeMillis().toString, 0)
    grnaDao.insert(row).toAwait
    Future {
       val result =   IgcOrItc.Run(path, params, "itc",f)
      println(result._2,result._1)
      grnaDao.updateState(f, result._1).toAwait
    }
    Ok(Json.toJson(f))
  }

  def getItcResult(id: String) = Action { implicit request =>
    val tableData = Array(("cor.xls", "Correlation coefficient matrix"), ("pvalue.xls", "P-valued matrix"), ("pandv.xls", "Correlation coefficient c value and p value analysis results"),
      ("p_star.xls", "A matrix of stars based on p-values"), ("heatmap.pdf", "Heatmap"))
    val params = IgcOrItc.GetParams(id )
    Ok(params ++ Json.obj("tableData" -> tableData))
  }

}
